﻿using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using WeavingCommon;

namespace NotifyPropertyWeaverMsBuildTask
{
    public class EqualityCheckWeaver
    {
        MsCoreReferenceFinder msCoreReferenceFinder;
        Logger logger;
        PropertyData propertyData;
        TypeNode typeNode;
        TypeEqualityFinder typeEqualityFinder;
        Collection<Instruction> instructions;

        public EqualityCheckWeaver(MsCoreReferenceFinder msCoreReferenceFinder, Logger logger, PropertyData propertyData, TypeNode typeNode, TypeEqualityFinder typeEqualityFinder)
        {
            this.msCoreReferenceFinder = msCoreReferenceFinder;
            this.logger = logger;
            this.propertyData = propertyData;
            this.typeNode = typeNode;
            this.typeEqualityFinder = typeEqualityFinder;
        }

        public void Execute()
        {
            var property = propertyData.PropertyDefinition;
            instructions = property.SetMethod.Body.Instructions;

            logger.LogMessage("\t\t\tCheckForEquality=" + propertyData.CheckForEquality);

            if (propertyData.CheckForEquality)
            {
                AddEqualityCheck();
            }

        }


        void AddEqualityCheck()
        {
            if (propertyData.BackingFieldReference == null)
            {
                CheckAgainstProperty();
            }
            else
            {
                CheckAgainstField();
            }
        }

        void CheckAgainstField()
        {
            var fieldReference = propertyData.BackingFieldReference.Resolve().GetGeneric();
            InjectEqualityCheck(Instruction.Create(OpCodes.Ldfld, fieldReference), fieldReference.FieldType);
        }


        void CheckAgainstProperty()
        {
            var propertyReference = propertyData.PropertyDefinition;
            var methodDefinition = propertyData.PropertyDefinition.GetMethod.GetGeneric();
            InjectEqualityCheck(Instruction.Create(OpCodes.Call, methodDefinition), propertyReference.PropertyType);
        }

        void InjectEqualityCheck(Instruction targetInstruction, TypeReference targetType)
        {
            var typeEqualityMethod = typeEqualityFinder.Find(targetType);

            var nopInstruction = instructions.First();
            if (nopInstruction.OpCode != OpCodes.Nop)
            {
                nopInstruction = Instruction.Create(OpCodes.Nop);
                instructions.Insert(0, nopInstruction);
            }

            if (typeEqualityMethod == null)
            {
                instructions.Prepend(
                    Instruction.Create(OpCodes.Box, targetType),
                    Instruction.Create(OpCodes.Ldarg_1),
                    Instruction.Create(OpCodes.Box, targetType),
                    Instruction.Create(OpCodes.Call, msCoreReferenceFinder.EqualsMethodReference),
                    Instruction.Create(OpCodes.Brfalse_S, nopInstruction),
                    Instruction.Create(OpCodes.Ret));
            }
            else
            {
                instructions.Prepend(
                    Instruction.Create(OpCodes.Ldarg_1),
                    Instruction.Create(OpCodes.Call, typeEqualityMethod),
                    Instruction.Create(OpCodes.Brfalse_S, nopInstruction),
                    Instruction.Create(OpCodes.Ret));
            }
            instructions.Prepend(
                Instruction.Create(OpCodes.Ldarg_0),
                targetInstruction);
        }

    }
}